package base_util

import (
	"github.com/goccy/go-json"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
)

// SliceExtract 提取切片中每个元素的某个属性或转换后的值，形成一个新切片
//
// 示例：
//
//	users := []User{{ID: 1}, {ID: 2}}
//	ids := SliceExtract(users, func(u User) int { return u.ID }) // 得到 []int{1, 2}
//
// 参数：
//
//	slice  - 源切片
//	mapper - 映射函数，用于将每个元素 T 映射为另一个类型 R
//
// 返回值：
//
//	新切片 []R，包含每个元素映射后的结果
func SliceExtract[T any, R any](slice []T, mapper func(T) R) []R {
	result := make([]R, 0, len(slice))
	for _, item := range slice {
		result = append(result, mapper(item))
	}
	return result
}

// SliceExtractDistinct 提取切片中每个元素经 mapper 映射后的值，并去重后返回结果切片。
// T 为输入切片元素类型，R 为映射后的结果类型，且必须是可比较类型（comparable）。
// 参数：
//   - slice: 输入切片（[]T）。
//   - mapper: 映射函数（func(T) R），提取目标字段。
//
// 返回值：
//   - 去重后的 []R 切片。
func SliceExtractDistinct[T any, R comparable](slice []T, mapper func(T) R) []R {
	result := make([]R, 0, len(slice)) // 预分配容量
	seen := make(map[R]struct{})       // 类型安全：map 的 key 明确为 R 类型，要求 R 是 comparable

	for _, item := range slice {
		key := mapper(item)
		if _, exists := seen[key]; !exists {
			seen[key] = struct{}{} // 添加到去重 map
			result = append(result, key)
		}
	}
	return result
}

// SliceToMap 将切片转换为 map 类型，通过指定 key 提取函数生成键
//
// 示例：
//
//	users := []User{{ID: 1}, {ID: 2}}
//	userMap := SliceToMap(users, func(u User) int { return u.ID })
//	// 得到 map[int]User{1: {...}, 2: {...}}
//
// 参数：
//
//	slice   - 源切片
//	keyFunc - 用于从每个元素中提取 key 的函数
//
// 返回值：
//
//	map[K]T，以 key 为键，原切片元素为值
func SliceToMap[T any, K comparable](slice []T, keyFunc func(T) K) map[K]T {
	result := make(map[K]T)
	for _, item := range slice {
		key := keyFunc(item)
		result[key] = item
	}
	return result
}

// SliceToMultiMap 将切片转换为 map[K][]T 类型，支持一对多映射
//
// 示例：
//
//	users := []User{{GroupID: 1}, {GroupID: 1}, {GroupID: 2}}
//	groupMap := SliceToMultiMap(users, func(u User) int { return u.GroupID })
//	// 得到 map[int][]User{1: [{...}, {...}], 2: [{...}]}
//
// 参数：
//
//	slice   - 源切片
//	keyFunc - 用于从每个元素中提取 key 的函数
//
// 返回值：
//
//	map[K][]T，key 对应多个值的映射关系
func SliceToMultiMap[T any, K comparable](slice []T, keyFunc func(T) K) map[K][]T {
	result := make(map[K][]T)
	for _, item := range slice {
		key := keyFunc(item)
		result[key] = append(result[key], item)
	}
	return result
}

// FormatSlice 将 JSON 格式的字符串数组解析为 Go 的字符串切片
//
// 示例：
//
//	jsonStr := `["a", "b", "c"]`
//	result, err := FormatSlice(jsonStr) // 得到 []string{"a", "b", "c"}
//
// 参数：
//
//	jsonArrStr - JSON 格式的数组字符串
//
// 返回值：
//   - result：解析后的字符串切片
//   - error：若解析失败返回错误信息，包含 zap 日志记录
func FormatSlice(jsonArrStr string) ([]string, error) {
	var result []string
	err := json.Unmarshal([]byte(jsonArrStr), &result)
	if err != nil {
		zap.S().Errorw("json数组字符串转换切片异常", "json数组值", jsonArrStr, "err", err.Error())
		return nil, LogicError("json数组字符串转换切片异常")
	}
	return result, nil
}

// ProtoMapForJsonMarshal 将 []proto.Message 批量转换为 []map[string]any
// 通用 JSON 场景
func ProtoMapForJsonMarshal(obj any) (map[string]any, error) {
	jsonBytes, err := json.Marshal(obj)
	if err != nil {
		zap.S().Errorw("protojson 序列化失败", "msg", obj, "错误", err)
		return nil, LogicError("Proto 转 JSON 失败")
	}

	// 反序列化为 map[string]any
	var item map[string]any
	if err := json.Unmarshal(jsonBytes, &item); err != nil {
		zap.S().Errorw("JSON 转 map 失败", "json", string(jsonBytes), "错误", err)
		return nil, LogicError("JSON 转 map 失败")
	}

	return item, nil
}

// ProtoMapForProtoMarshal 使用 protojson 将 []proto.Message 转为 []map[string]any
// 高性能场景（Protobuf 结构体）
func ProtoMapForProtoMarshal(obj proto.Message) (map[string]any, error) {
	//将 proto.Message 转为 JSON 字符串
	jsonBytes, err := protojson.Marshal(obj)
	if err != nil {
		zap.S().Errorw("protojson 序列化失败", "msg", obj, "错误", err)
		return nil, LogicError("Proto 转 JSON 失败")
	}

	// JSON 反序列化为 map[string]any
	var item map[string]any
	if err := json.Unmarshal(jsonBytes, &item); err != nil {
		zap.S().Errorw("JSON 转 map 失败", "json", string(jsonBytes), "错误", err)
		return nil, LogicError("JSON 转 map 失败")
	}
	return item, nil
}
